package othello.server.memcacheutil;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.slim3.memcache.Memcache;

import othello.shared.model.GameStep;
import othello.util.SynchronizedRunner;

/**
 * <p>
 * ゲームボードの全ステップのキャッシュを取り扱うためのクラス
 * </p>
 * 
 * @author onjiro.mohyahya
 */
public class GameStateCacher {

	private static final Logger LOGGER = Logger.getLogger(GameStateCacher.class
			.getName());

	/**
	 * 取り扱う対象のゲームボードのID
	 */
	private final String gameBoardId;

	/**
	 * Syncronized ブロック実装
	 */
	private final SynchronizedRunner syncRunner;

	/**
	 * コンストラクターを使用してのインスタンスの生成禁止
	 */
	private GameStateCacher(String id) {
		this.gameBoardId = id;
		this.syncRunner = new SynchronizedRunner("entryLock:GameBoard:"
				+ this.gameBoardId);
	}

	/**
	 * ゲームボードのIDを引数に取るスタティックファクトリーメソッド
	 * 
	 * @param gameBoardId
	 *            ゲームボードのID
	 * @return 指定されたゲームボードの情報のキャッシュの代理
	 */
	public static GameStateCacher getInstanceFor(String gameBoardId) {
		return new GameStateCacher(gameBoardId);
	}

	/**
	 * ゲームボードの次のステップ情報を登録します。
	 * 
	 * @param nextStep
	 *            ゲームボードに登録する次のステップ情報
	 * @return true:登録OK, false:登録NG
	 */
	public boolean entryStep(final GameStep nextStep) {
		// ステップ情報の登録を排他的に実行する。
		try {
			this.syncRunner.run(new Runnable() {
				@Override
				public void run() {
					// 整合性が一致しない場合は処理中断
					List<GameStep> allSteps = GameStepsCacheManager
							.get(gameBoardId);
					if (allSteps == null) {
						allSteps = new ArrayList<GameStep>(8 * 8);
					}
					if (nextStep.getStepNumber() != allSteps.size() + 1)
						throw new UpdateyFailed("step number expected \""
								+ (allSteps.size() + 1) + "\", but \""
								+ nextStep.getStepNumber() + "\"");

					allSteps.add(nextStep);
					GameStepsCacheManager.put(gameBoardId, allSteps);
				}
			});
		} catch (UpdateyFailed e) {
			Object[] parameters = { this.gameBoardId, nextStep };
			LOGGER.log(Level.WARNING, "entryStep failed by " + e.getMessage()
					+ ".gameBoardId={0}, nextStep={1}", parameters);
			return false;
		}
		return true;
	}

	/**
	 * 指定されたステップよりあとに登録のあったステップすべてを返します。
	 * 
	 * @param step
	 *            取得開始ステップ
	 * @return 指定されたステップよりあとのステップのリスト
	 */
	public List<GameStep> getStepsFrom(int stepNumber) {
		List<GameStep> ret = new ArrayList<GameStep>(1);

		List<GameStep> allSteps = GameStepsCacheManager.get(this.gameBoardId);
		if (allSteps != null && allSteps.size() > stepNumber) {
			ret = new ArrayList<GameStep>(allSteps.subList(stepNumber, allSteps
					.size()));
		}
		return ret;
	}

	/**
	 * ゲームボードのステップ情報を削除
	 * 
	 * @return 削除成功時true
	 */
	public boolean delete() {
		try {
			syncRunner.run(new Runnable() {
				@Override
				public void run() {
					GameStepsCacheManager.delete(gameBoardId);
				}
			});
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	/**
	 * Memcacheによるゲームボードのキャッシュを取り扱うクラス
	 */
	private static final class GameStepsCacheManager {

		/**
		 * Memcacheに指定されたゲームボードの全ステップを登録します。
		 * 
		 * @param gameBoardId
		 * @param gameSteps
		 */
		public static void put(String gameBoardId, List<GameStep> gameSteps) {
			Object[] inputs = { gameBoardId, gameSteps };
			LOGGER.entering(Memcache.class.getName(), "put", inputs);
			Memcache.put(gameBoardId, gameSteps);
		}

		/**
		 * Memcacheから指定されたIDのゲームボードの全ステップを取得します。
		 * 
		 * @param gameBoardId
		 * @return
		 */
		public static List<GameStep> get(String gameBoardId) {
			LOGGER.entering(Memcache.class.getName(), "get", gameBoardId);
			List<GameStep> ret = Memcache.get(gameBoardId);
			LOGGER.exiting(Memcache.class.getName(), "get", ret);
			return ret;
		}

		public static boolean delete(String gameBoardId) {
			LOGGER.entering(Memcache.class.getName(), "delete", gameBoardId);
			boolean ret = Memcache.delete(gameBoardId);
			LOGGER.exiting(Memcache.class.getName(), "delete", ret);
			return ret;

		}
	}

	/**
	 * キャッシュの更新失敗例外
	 */
	@SuppressWarnings("serial")
	private static final class UpdateyFailed extends RuntimeException {
		public UpdateyFailed(String message) {
			super(message);
		}
	}
}
